#include "heaptimer.h"

void HeapTimer::add(int id, int timeout, const TimeoutCallBack &cb)
{
    assert(id >= 0);
    if (ref_.count(id) == 0)
    {
        heap_.push_back({id, Clock::now() + (MS)timeout, cb});
        ref_[id] = heap_.size() - 1;
        siftup_(ref_[id]);
    }
    else
    {
        size_t i = ref_[id];
        heap_[i].cb = cb;
        heap_[i].expires = Clock::now() + (MS)timeout;
        if (!siftdown_(i, heap_.size()))
            siftup_(i);
    }
}

void HeapTimer::pop()
{
    assert(heap_.size() > 0);
    del_(0);
}

void HeapTimer::adjust(int id, int newExpires)
{
    assert(!heap_.empty() && ref_.count(id) > 0);

    size_t i = ref_[id];
    heap_[i].expires = Clock::now() + MS(newExpires);
    if (!siftdown_(i, heap_.size()))
        siftup_(i);
}

void HeapTimer::dowork(int id)
{
    assert(ref_.count(id) > 0);
    size_t i = ref_[id];
    heap_[i].cb();
    del_(i);
}

void HeapTimer::clear()
{
    heap_.clear();
    ref_.clear();
}

void HeapTimer::tick()
{
    while (heap_.size())
    {
        TimerNode node = heap_.front();
        if (std::chrono::duration_cast<MS>(node.expires - Clock::now()).count() > 0)
            break;
        node.cb();
        pop();
    }
}

int HeapTimer::GetNextTick()
{
    tick();
    size_t t = -1;
    if (heap_.size())
    {
        TimerNode node = heap_.front();
        t = std::chrono::duration_cast<MS>(node.expires - Clock::now()).count();
        if (t < 0)
            t = 0;
    }
    return t;
}

bool HeapTimer::siftdown_(size_t i, size_t n)
{
    assert(i >= 0 && i < heap_.size());
    assert(n >= 0 && n <= heap_.size());
    int idx = i;
    int j = i * 2 + 1;
    while (j < n)
    {
        if (j + 1 < n && heap_[j + 1] < heap_[j])
            j++;
        if (heap_[i] < heap_[j])
            break;
        SwapNode(i, j);
        i = j;
        j = i * 2 + 1;
    }
    return i > idx;
}

void HeapTimer::siftup_(size_t i)
{
    assert(i >= 0 && i < heap_.size());
    int j = (i - 1) / 2;
    while (j >= 0 && i)
    {
        if (heap_[j] <= heap_[i])
            break;
        i = j;
        j = (i - 1) / 2;
    }
}

void HeapTimer::del_(size_t idx)
{
    size_t last = heap_.size() - 1;
    SwapNode(idx, last);
    dowork(last);
    ref_.erase(heap_[last].id);
    heap_.pop_back();
    if (!siftdown_(idx, heap_.size()))
    {
        siftup_(idx);
    }
}

void HeapTimer::SwapNode(size_t i, size_t j)
{
    assert(i >= 0 && i < heap_.size());
    assert(j >= 0 && j < heap_.size());
    std::swap(heap_[i], heap_[j]);
    ref_[heap_[i].id] = i;
    ref_[heap_[j].id] = j;
}